win32: Better crossing events and grab destination reporting
authorAlexander Larsson <alexl@redhat.com>
Wed, 19 Oct 2011 19:44:38 +0000 (21:44 +0200)
committerAlexander Larsson <alexl@redhat.com>
Thu, 10 Nov 2011 16:40:55 +0000 (17:40 +0100)
We new report to the right window during !owner_event grabs, and
we send proper enter and leave events.

gdk/win32/gdkevents-win32.c

index b33b1764cf2e0884f465dac519769dc7bb9af2a1..5ea42453da20161d5d3762fc3937d730a7da29c2 100644 (file)
@@ -119,7 +119,7 @@ static GSourceFuncs event_funcs = {
 
 GPollFD event_poll_fd;
 
-static GdkWindow *current_toplevel = NULL;
+static GdkWindow *mouse_window = NULL;
 static gint current_x, current_y;
 static gint current_root_x, current_root_y;
 static UINT client_message;
@@ -137,13 +137,6 @@ static UINT     sync_timer = 0;
 
 static int debug_indent = 0;
 
-static void
-synthesize_enter_or_leave_event (GdkWindow     *window,
-                                MSG            *msg,
-                                GdkEventType    type,
-                                GdkCrossingMode mode,
-                                GdkNotifyType detail);
-
 static void
 assign_object (gpointer lhsp,
               gpointer rhs)
@@ -506,48 +499,47 @@ static GdkWindow *
 find_window_for_mouse_event (GdkWindow* reported_window,
                             MSG*       msg)
 {
-  HWND hwnd;
-  POINTS points;
   POINT pt;
-  GdkWindow* other_window = NULL;
   GdkDeviceManagerWin32 *device_manager;
+  GdkWindow *event_window;
+  HWND hwnd;
+  RECT rect;
+  GdkDeviceGrabInfo *grab;
 
   device_manager = GDK_DEVICE_MANAGER_WIN32 (gdk_display_get_device_manager (_gdk_display));
 
-  if (!_gdk_display_get_last_device_grab (_gdk_display, device_manager->core_pointer))
+  grab = _gdk_display_get_last_device_grab (_gdk_display, device_manager->core_pointer);
+  if (grab == NULL)
     return reported_window;
 
-  points = MAKEPOINTS (msg->lParam);
-  pt.x = points.x;
-  pt.y = points.y;
-  ClientToScreen (msg->hwnd, &pt);
-
-  hwnd = WindowFromPoint (pt);
+  pt = msg->pt;
 
-  if (hwnd != NULL)
+  if (!grab->owner_events)
+    event_window = grab->native_window;
+  else
     {
-      RECT rect;
-
-      GetClientRect (hwnd, &rect);
-      ScreenToClient (hwnd, &pt);
-      if (!PtInRect (&rect, pt))
-       return _gdk_root;
+      event_window = NULL;
+      hwnd = WindowFromPoint (pt);
+      if (hwnd != NULL)
+       {
+         POINT client_pt = pt;
 
-      other_window = gdk_win32_handle_table_lookup (hwnd);
+         ScreenToClient (hwnd, &client_pt);
+         GetClientRect (hwnd, &rect);
+         if (PtInRect (&rect, client_pt))
+           event_window = gdk_win32_handle_table_lookup (hwnd);
+       }
+      if (event_window == NULL)
+       event_window = grab->native_window;
     }
 
-  if (other_window == NULL)
-    return _gdk_root;
-
   /* need to also adjust the coordinates to the new window */
-  pt.x = points.x;
-  pt.y = points.y;
-  ClientToScreen (msg->hwnd, &pt);
-  ScreenToClient (GDK_WINDOW_HWND (other_window), &pt);
+  ScreenToClient (GDK_WINDOW_HWND (event_window), &pt);
+
   /* ATTENTION: need to update client coords */
   msg->lParam = MAKELPARAM (pt.x, pt.y);
 
-  return other_window;
+  return event_window;
 }
 
 static void
@@ -1137,39 +1129,230 @@ do_show_window (GdkWindow *window, gboolean hide_window)
 }
 
 static void
-synthesize_enter_or_leave_event (GdkWindow        *window,
-                                 MSG             *msg,
-                                 GdkEventType     type,
-                                 GdkCrossingMode   mode,
-                                 GdkNotifyType     detail)
+send_crossing_event (GdkDisplay                 *display,
+                    GdkWindow                  *window,
+                    GdkEventType                type,
+                    GdkCrossingMode             mode,
+                    GdkNotifyType               notify_type,
+                    GdkWindow                  *subwindow,
+                    POINT                      *screen_pt,
+                    GdkModifierType             mask,
+                    guint32                     time_)
 {
   GdkEvent *event;
+  GdkDeviceGrabInfo *grab;
+  GdkDeviceManagerWin32 *device_manager;
   POINT pt;
 
-  pt = msg->pt;
+  device_manager = GDK_DEVICE_MANAGER_WIN32 (gdk_display_get_device_manager (display));
+
+  grab = _gdk_display_has_device_grab (display, device_manager->core_pointer, 0);
+
+  if (grab != NULL &&
+      !grab->owner_events &&
+      mode != GDK_CROSSING_UNGRAB)
+    {
+      /* !owner_event => only report events wrt grab window, ignore rest */
+      if ((GdkWindow *)window != grab->native_window)
+       return;
+    }
+
+  pt = *screen_pt;
   ScreenToClient (GDK_WINDOW_HWND (window), &pt);
   
   event = gdk_event_new (type);
   event->crossing.window = window;
-  event->crossing.subwindow = NULL;
-  event->crossing.time = _gdk_win32_get_next_tick (msg->time);
+  event->crossing.subwindow = subwindow;
+  event->crossing.time = _gdk_win32_get_next_tick (time_);
   event->crossing.x = pt.x;
   event->crossing.y = pt.y;
-  event->crossing.x_root = msg->pt.x + _gdk_offset_x;
-  event->crossing.y_root = msg->pt.y + _gdk_offset_y;
+  event->crossing.x_root = screen_pt->x + _gdk_offset_x;
+  event->crossing.y_root = screen_pt->y + _gdk_offset_y;
+  event->crossing.mode = mode;
+  event->crossing.detail = notify_type;
   event->crossing.mode = mode;
-  event->crossing.detail = detail;
-  event->crossing.focus = TRUE; /* FIXME: Set correctly */
-  event->crossing.state = 0;   /* FIXME: Set correctly */
+  event->crossing.detail = notify_type;
+  event->crossing.focus = FALSE;
+  event->crossing.state = mask;
   gdk_event_set_device (event, _gdk_display->core_pointer);
 
   _gdk_win32_append_event (event);
-  
+
   if (type == GDK_ENTER_NOTIFY &&
       window->extension_events != 0)
     _gdk_device_wintab_update_window_coords (window);
 }
 
+static GdkWindow *
+get_native_parent (GdkWindow *window)
+{
+  if (window->parent != NULL)
+    return window->parent->impl_window;
+  return NULL;
+}
+
+static GdkWindow *
+find_common_ancestor (GdkWindow *win1,
+                     GdkWindow *win2)
+{
+  GdkWindow *tmp;
+  GList *path1 = NULL, *path2 = NULL;
+  GList *list1, *list2;
+
+  tmp = win1;
+  while (tmp != NULL && tmp->window_type != GDK_WINDOW_ROOT)
+    {
+      path1 = g_list_prepend (path1, tmp);
+      tmp = get_native_parent (tmp);
+    }
+
+  tmp = win2;
+  while (tmp != NULL && tmp->window_type != GDK_WINDOW_ROOT)
+    {
+      path2 = g_list_prepend (path2, tmp);
+      tmp = get_native_parent (tmp);
+    }
+
+  list1 = path1;
+  list2 = path2;
+  tmp = NULL;
+  while (list1 && list2 && (list1->data == list2->data))
+    {
+      tmp = (GdkWindow *)list1->data;
+      list1 = g_list_next (list1);
+      list2 = g_list_next (list2);
+    }
+  g_list_free (path1);
+  g_list_free (path2);
+
+  return tmp;
+}
+
+void
+synthesize_crossing_events (GdkDisplay                 *display,
+                           GdkWindow                  *src,
+                           GdkWindow                  *dest,
+                           GdkCrossingMode             mode,
+                           POINT                      *screen_pt,
+                           GdkModifierType             mask,
+                           guint32                     time_,
+                           gboolean                    non_linear)
+{
+  GdkWindow *c;
+  GdkWindow *win, *last, *next;
+  GList *path, *list;
+  GdkWindow *a;
+  GdkWindow *b;
+  GdkNotifyType notify_type;
+
+  a = src;
+  b = dest;
+  if (a == b)
+    return; /* No crossings generated between src and dest */
+
+  c = find_common_ancestor (a, b);
+
+  non_linear |= (c != a) && (c != b);
+
+  if (a) /* There might not be a source (i.e. if no previous pointer_in_window) */
+    {
+      /* Traverse up from a to (excluding) c sending leave events */
+      if (non_linear)
+       notify_type = GDK_NOTIFY_NONLINEAR;
+      else if (c == a)
+       notify_type = GDK_NOTIFY_INFERIOR;
+      else
+       notify_type = GDK_NOTIFY_ANCESTOR;
+      send_crossing_event (display,
+                          a, GDK_LEAVE_NOTIFY,
+                          mode,
+                          notify_type,
+                          NULL,
+                          screen_pt,
+                          mask, time_);
+
+      if (c != a)
+       {
+         if (non_linear)
+           notify_type = GDK_NOTIFY_NONLINEAR_VIRTUAL;
+         else
+           notify_type = GDK_NOTIFY_VIRTUAL;
+
+         last = a;
+         win = get_native_parent (a);
+         while (win != c && win->window_type != GDK_WINDOW_ROOT)
+           {
+             send_crossing_event (display,
+                                  win, GDK_LEAVE_NOTIFY,
+                                  mode,
+                                  notify_type,
+                                  (GdkWindow *)last,
+                                  screen_pt,
+                                  mask, time_);
+
+             last = win;
+             win = get_native_parent (win);
+           }
+       }
+    }
+
+  if (b) /* Might not be a dest, e.g. if we're moving out of the window */
+    {
+      /* Traverse down from c to b */
+      if (c != b)
+       {
+         path = NULL;
+         win = get_native_parent (b);
+         while (win != c && win->window_type != GDK_WINDOW_ROOT)
+           {
+             path = g_list_prepend (path, win);
+             win = get_native_parent (win);
+           }
+
+         if (non_linear)
+           notify_type = GDK_NOTIFY_NONLINEAR_VIRTUAL;
+         else
+           notify_type = GDK_NOTIFY_VIRTUAL;
+
+         list = path;
+         while (list)
+           {
+             win = (GdkWindow *)list->data;
+             list = g_list_next (list);
+             if (list)
+               next = (GdkWindow *)list->data;
+             else
+               next = b;
+
+             send_crossing_event (display,
+                                  win, GDK_ENTER_NOTIFY,
+                                  mode,
+                                  notify_type,
+                                  next,
+                                  screen_pt,
+                                  mask, time_);
+           }
+         g_list_free (path);
+       }
+
+
+      if (non_linear)
+       notify_type = GDK_NOTIFY_NONLINEAR;
+      else if (c == a)
+       notify_type = GDK_NOTIFY_ANCESTOR;
+      else
+       notify_type = GDK_NOTIFY_INFERIOR;
+
+      send_crossing_event (display,
+                          b, GDK_ENTER_NOTIFY,
+                          mode,
+                          notify_type,
+                          NULL,
+                          screen_pt,
+                          mask, time_);
+    }
+}
+
 /* The check_extended flag controls whether to check if the windows want
  * events from extended input devices and if the message should be skipped
  * because an extended input device is active
@@ -1716,7 +1899,7 @@ gdk_event_translate (MSG  *msg,
   GdkWindow *window = NULL;
   GdkWindowImplWin32 *impl;
 
-  GdkWindow *orig_window, *new_window, *toplevel;
+  GdkWindow *orig_window, *new_window;
 
   GdkDeviceManager *device_manager;
 
@@ -2157,7 +2340,29 @@ gdk_event_translate (MSG  *msg,
 
          /* We keep the implicit grab until no buttons at all are held down */
          if ((state & GDK_ANY_BUTTON_MASK & ~(GDK_BUTTON1_MASK << (button - 1))) == 0)
-           ReleaseCapture ();
+           {
+             ReleaseCapture ();
+
+             new_window = NULL;
+             hwnd = WindowFromPoint (msg->pt);
+             if (hwnd != NULL)
+               {
+                 POINT client_pt = msg->pt;
+
+                 ScreenToClient (hwnd, &client_pt);
+                 GetClientRect (hwnd, &rect);
+                 if (PtInRect (&rect, client_pt))
+                   new_window = gdk_win32_handle_table_lookup (hwnd);
+               }
+             synthesize_crossing_events (_gdk_display,
+                                         pointer_grab->native_window, new_window,
+                                         GDK_CROSSING_UNGRAB,
+                                         &msg->pt,
+                                         0, /* TODO: Set right mask */
+                                         msg->time,
+                                         FALSE);
+             assign_object (&mouse_window, new_window);
+           }
        }
 
       generate_button_event (GDK_BUTTON_RELEASE, button,
@@ -2172,22 +2377,50 @@ gdk_event_translate (MSG  *msg,
                         (gpointer) msg->wParam,
                         GET_X_LPARAM (msg->lParam), GET_Y_LPARAM (msg->lParam)));
 
-      assign_object (&window, find_window_for_mouse_event (window, msg));
-      toplevel = gdk_window_get_toplevel (window);
-      if (current_toplevel != toplevel)
+      new_window = window;
+
+      if (pointer_grab != NULL)
+       {
+         POINT pt;
+         pt = msg->pt;
+
+         new_window = NULL;
+         hwnd = WindowFromPoint (pt);
+         if (hwnd != NULL)
+           {
+             POINT client_pt = pt;
+
+             ScreenToClient (hwnd, &client_pt);
+             GetClientRect (hwnd, &rect);
+             if (PtInRect (&rect, client_pt))
+               new_window = gdk_win32_handle_table_lookup (hwnd);
+           }
+
+         if (!pointer_grab->owner_events &&
+             new_window != NULL &&
+             new_window != pointer_grab->native_window)
+           new_window = NULL;
+       }
+
+      if (mouse_window != new_window)
        {
-         GDK_NOTE (EVENTS, g_print (" toplevel %p -> %p", 
-             current_toplevel ? GDK_WINDOW_HWND (current_toplevel) : NULL, 
-             toplevel ? GDK_WINDOW_HWND (toplevel) : NULL));
-         if (current_toplevel)
-           synthesize_enter_or_leave_event (current_toplevel, msg,
-                                       GDK_LEAVE_NOTIFY, GDK_CROSSING_NORMAL, GDK_NOTIFY_ANCESTOR);
-         synthesize_enter_or_leave_event (toplevel, msg,
-                                     GDK_ENTER_NOTIFY, GDK_CROSSING_NORMAL, GDK_NOTIFY_ANCESTOR);
-         assign_object (&current_toplevel, toplevel);
-         track_mouse_event (TME_LEAVE, GDK_WINDOW_HWND (toplevel));
+         GDK_NOTE (EVENTS, g_print (" mouse_sinwod %p -> %p",
+                                    mouse_window ? GDK_WINDOW_HWND (mouse_window) : NULL, 
+                                    new_window ? GDK_WINDOW_HWND (new_window) : NULL));
+         synthesize_crossing_events (_gdk_display,
+                                     mouse_window, new_window,
+                                     GDK_CROSSING_NORMAL,
+                                     &msg->pt,
+                                     0, /* TODO: Set right mask */
+                                     msg->time,
+                                     FALSE);
+         assign_object (&mouse_window, new_window);
+         if (new_window != NULL)
+           track_mouse_event (TME_LEAVE, GDK_WINDOW_HWND (new_window));
        }
 
+      assign_object (&window, find_window_for_mouse_event (window, msg));
+
       /* If we haven't moved, don't create any GDK event. Windows
        * sends WM_MOUSEMOVE messages after a new window is shows under
        * the mouse, even if the mouse hasn't moved. This disturbs gtk.
@@ -2226,22 +2459,27 @@ gdk_event_translate (MSG  *msg,
       GDK_NOTE (EVENTS, g_print (" %d (%ld,%ld)",
                                 HIWORD (msg->wParam), msg->pt.x, msg->pt.y));
 
-      if (!gdk_win32_handle_table_lookup (WindowFromPoint (msg->pt)))
-       {
-         /* we are only interested if we don't know the new window */
-         if (current_toplevel)
-           synthesize_enter_or_leave_event (current_toplevel, msg,
-                                            GDK_LEAVE_NOTIFY, GDK_CROSSING_NORMAL, GDK_NOTIFY_ANCESTOR);
-         assign_object (&current_toplevel, NULL);
-       }
-      else if (window != gdk_window_get_toplevel (window)) /* xxx: only for native child windows? */
+      new_window = NULL;
+      hwnd = WindowFromPoint (msg->pt);
+      if (hwnd != NULL)
        {
-         /* XXX: this used to be ignored pre-csw, but I think we need at least some 
-          * of the leave events */
-         synthesize_enter_or_leave_event (window, msg,
-                                          GDK_LEAVE_NOTIFY, GDK_CROSSING_NORMAL, GDK_NOTIFY_ANCESTOR);
+         POINT client_pt = msg->pt;
+
+         ScreenToClient (hwnd, &client_pt);
+         GetClientRect (hwnd, &rect);
+         if (PtInRect (&rect, client_pt))
+           new_window = gdk_win32_handle_table_lookup (hwnd);
        }
 
+      synthesize_crossing_events (_gdk_display,
+                                 mouse_window, new_window,
+                                 GDK_CROSSING_NORMAL,
+                                 &msg->pt,
+                                 0, /* TODO: Set right mask */
+                                 msg->time,
+                                 FALSE);
+      assign_object (&mouse_window, new_window);
+
       return_val = TRUE;
       break;